﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Agile.NetWork.Serial;

namespace Agile.NetWork.Profinet.Panasonic
{
    /// <summary>
    /// 松下PLC的数据交互协议，采用Mewtocol协议通讯
    /// </summary>
    public class PanasonicMewtocol : SerialDeviceBase<RegularByteTransform>
    {
        #region Constructor

        /// <summary>
        /// 实例化一个默认的松下PLC通信对象，默认站号为1
        /// </summary>
        /// <param name="station">站号信息，默认为0xEE</param>
        public PanasonicMewtocol(byte station = 238)
        {
            this.Station = station;
            this.ByteTransform.DataFormat = DataFormat.DCBA;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// 设备的目标站号
        /// </summary>
        public byte Station { get; set; }

        #endregion

        #region Read Write Override

        /// <summary>
        /// 从松下PLC中读取数据
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="length">长度</param>
        /// <returns>返回数据信息</returns>
        public override Result<byte[]> Read(string address, ushort length)
        {
            // 创建指令
            Result<byte[]> command = BuildReadCommand(Station, address, length);
            if (!command.Success) return command;

            // 数据交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 提取数据
            return ExtraActualData(read.Data);
        }

        /// <summary>
        /// 将数据写入到松下PLC中
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="value">真实数据</param>
        /// <returns>是否写入成功</returns>
        public override Result Write(string address, byte[] value)
        {
            // 创建指令
            Result<byte[]> command = BuildWriteCommand(Station, address, value);
            if (!command.Success) return command;

            // 数据交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 提取结果
            return ExtraActualData(read.Data);
        }

        #endregion

        #region Read Write Bool

        /// <summary>
        /// 批量读取松下PLC的位地址
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="length">数据长度</param>
        /// <returns>读取结果对象</returns>
        public Result<bool[]> ReadBool(string address, ushort length)
        {
            // 读取数据
            Result<byte[]> read = Read(address, length);
            if (!read.Success) return Result.CreateFailedResult<bool[]>(read);

            // 提取bool
            byte[] buffer = Utils.BytesReverseByWord(read.Data);
            return Result.CreateSuccessResult(Utils.ByteToBoolArray(read.Data, length));
        }

        /// <summary>
        /// 读取单个的Bool数据
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <returns>读取结果对象</returns>
        public Result<bool> ReadBool(string address)
        {
            // 读取数据
            Result<bool[]> read = ReadBool(address, 1);
            if (!read.Success) return Result.CreateFailedResult<bool>(read);

            return Result.CreateSuccessResult(read.Data[0]);
        }

        /// <summary>
        /// 写入bool数据信息
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="values">数据值信息</param>
        /// <returns>返回是否成功的结果对象</returns>
        public Result Write(string address, bool[] values)
        {
            // 计算字节数据
            byte[] buffer = Utils.BoolArrayToByte(values);

            // 创建指令
            Result<byte[]> command = BuildWriteCommand(Station, address, Utils.BytesReverseByWord(buffer), (short)values.Length);
            if (!command.Success) return command;

            // 数据交互
            Result<byte[]> read = ReadBase(command.Data);
            if (!read.Success) return read;

            // 提取结果
            return ExtraActualData(read.Data);
        }

        /// <summary>
        /// 写入bool数据数据
        /// </summary>
        /// <param name="address">起始地址</param>
        /// <param name="value">True还是False</param>
        /// <returns>返回是否成功的结果对象</returns>
        public Result Write(string address, bool value)
        {
            return Write(address, new bool[] { value });
        }

        #endregion

        #region Bulid Read Command

        private static string CalculateCrc(StringBuilder sb)
        {
            byte tmp = 0;
            tmp = (byte)sb[0];
            for (int i = 1; i < sb.Length; i++)
            {
                tmp ^= (byte)sb[i];
            }
            return Utils.ByteToHexString(new byte[] { tmp });
        }

        /// <summary>
        /// 解析数据地址，解析出地址类型，起始地址，DB块的地址
        /// </summary>
        /// <param name="address">数据地址</param>
        /// <returns>解析出地址类型，起始地址，是否位读取</returns>
        private static Result<string, int> AnalysisAddress(string address)
        {
            var result = new Result<string, int>();
            try
            {
                result.Data2 = 0;
                if (address.StartsWith("IX") || address.StartsWith("ix"))
                {
                    result.Data1 = "IX";
                    result.Data2 = int.Parse(address.Substring(2));
                }
                else if (address.StartsWith("IY") || address.StartsWith("iy"))
                {
                    result.Data1 = "IY";
                    result.Data2 = int.Parse(address.Substring(2));
                }
                else if (address.StartsWith("ID") || address.StartsWith("id"))
                {
                    result.Data1 = "ID";
                    result.Data2 = int.Parse(address.Substring(2));
                }
                else if (address[0] == 'X' || address[0] == 'x')
                {
                    result.Data1 = "X";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'Y' || address[0] == 'y')
                {
                    result.Data1 = "Y";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'R' || address[0] == 'r')
                {
                    result.Data1 = "R";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'T' || address[0] == 't')
                {
                    result.Data1 = "T";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'C' || address[0] == 'c')
                {
                    result.Data1 = "C";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'L' || address[0] == 'l')
                {
                    result.Data1 = "L";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'D' || address[0] == 'd')
                {
                    result.Data1 = "D";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'F' || address[0] == 'f')
                {
                    result.Data1 = "F";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'S' || address[0] == 's')
                {
                    result.Data1 = "S";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else if (address[0] == 'K' || address[0] == 'k')
                {
                    result.Data1 = "K";
                    result.Data2 = ushort.Parse(address.Substring(1));
                }
                else
                {
                    throw new Exception( "输入的类型不支持，请重新输入");
                }
            }
            catch (Exception ex)
            {
                result.Message = ex.Message;
                return result;
            }

            result.Success = true;
            return result;
        }

        /// <summary>
        /// 创建读取离散触点的报文指令
        /// </summary>
        /// <param name="address">地址信息</param>
        /// <returns>包含是否成功的结果对象</returns>
        public static Result<byte[]> BuildReadMultiCoil(string[] address)
        {
            // 参数检查
            if (address == null) return new Result<byte[]>("address is not allowed null");
            if (address.Length < 1 || address.Length > 8) return new Result<byte[]>("length must be 1-8");

            StringBuilder sb = new StringBuilder("%EE#RCP");
            sb.Append(address.Length.ToString());
            for (int i = 0; i < address.Length; i++)
            {
                // 解析地址
                Result<string, int> analysis = AnalysisAddress(address[i]);
                if (!analysis.Success) return Result.CreateFailedResult<byte[]>(analysis);

                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2);
            }

            sb.Append(CalculateCrc(sb));
            sb.Append('\u000D');

            return Result.CreateSuccessResult(Encoding.ASCII.GetBytes(sb.ToString()));
        }

        /// <summary>
        /// 创建写入离散触点的报文指令
        /// </summary>
        /// <param name="address">地址信息</param>
        /// <param name="values">bool值数组</param>
        /// <returns>包含是否成功的结果对象</returns>
        public static Result<byte[]> BuildWriteMultiCoil(string[] address, bool[] values)
        {
            // 参数检查
            if (address == null || values == null) return new Result<byte[]>("address and values is not allowed null");
            if (address.Length < 1 || address.Length > 8) return new Result<byte[]>("address length must be 1-8");
            if (address.Length != values.Length) return new Result<byte[]>("address and values length must be same");

            StringBuilder sb = new StringBuilder("%EE#WCP");
            sb.Append(address.Length.ToString());
            for (int i = 0; i < address.Length; i++)
            {
                // 解析地址
                Result<string, int> analysis = AnalysisAddress(address[i]);
                if (!analysis.Success) return Result.CreateFailedResult<byte[]>(analysis);

                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2);

                sb.Append(values[i] ? '1' : '0');
            }

            sb.Append(CalculateCrc(sb));
            sb.Append('\u000D');

            return Result.CreateSuccessResult(Encoding.ASCII.GetBytes(sb.ToString()));
        }


        /// <summary>
        /// 创建批量读取触点的报文指令
        /// </summary>
        /// <param name="station">站号信息</param>
        /// <param name="address">地址信息</param>
        /// <param name="length">数据长度</param>
        /// <returns>包含是否成功的结果对象</returns>
        public static Result<byte[]> BuildReadCommand(byte station, string address, ushort length)
        {
            // 参数检查
            if (address == null) return new Result<byte[]>("地址参数不允许为空");

            // 解析地址
            Result<string, int> analysis = AnalysisAddress(address);
            if (!analysis.Success) return Result.CreateFailedResult<byte[]>(analysis);

            StringBuilder sb = new StringBuilder("%");
            sb.Append(station.ToString("X2"));
            sb.Append("#");

            if (analysis.Data1 == "X" || analysis.Data1 == "Y" || analysis.Data1 == "R" || analysis.Data1 == "L")
            {
                sb.Append("RCC");
                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2.ToString("D4"));
                sb.Append((analysis.Data2 + length - 1).ToString("D4"));
            }
            else if (analysis.Data1 == "D" || analysis.Data1 == "L" || analysis.Data1 == "F")
            {
                sb.Append("RD");
                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2.ToString("D5"));
                sb.Append((analysis.Data2 + length - 1).ToString("D5"));
            }
            else if (analysis.Data1 == "IX" || analysis.Data1 == "IY" || analysis.Data1 == "ID")
            {
                sb.Append("RD");
                sb.Append(analysis.Data1);
                sb.Append("000000000");
            }
            else if (analysis.Data1 == "C" || analysis.Data1 == "T")
            {
                sb.Append("RS");
                sb.Append(analysis.Data2.ToString("D4"));
                sb.Append((analysis.Data2 + length - 1).ToString("D4"));
            }
            else
            {
                return new Result<byte[]>("输入的类型不支持，请重新输入");
            }

            sb.Append(CalculateCrc(sb));
            sb.Append('\u000D');

            return Result.CreateSuccessResult(Encoding.ASCII.GetBytes(sb.ToString()));
        }

        /// <summary>
        /// 创建批量读取触点的报文指令
        /// </summary>
        /// <param name="station">设备站号</param>
        /// <param name="address">地址信息</param>
        /// <param name="values">数据值</param>
        /// <param name="length">数据长度</param>
        /// <returns>包含是否成功的结果对象</returns>
        public static Result<byte[]> BuildWriteCommand(byte station, string address, byte[] values, short length = -1)
        {
            // 参数检查
            if (address == null) return new Result<byte[]>("地址参数不允许为空");

            // 解析地址
            Result<string, int> analysis = AnalysisAddress(address);
            if (!analysis.Success) return Result.CreateFailedResult<byte[]>(analysis);

            // 确保偶数长度
            values = Utils.ArrayExpandToLengthEven(values);
            if (length == -1) length = (short)(values.Length / 2);

            StringBuilder sb = new StringBuilder("%");
            sb.Append(station.ToString("X2"));
            sb.Append("#");

            if (analysis.Data1 == "X" || analysis.Data1 == "Y" || analysis.Data1 == "R" || analysis.Data1 == "L")
            {
                sb.Append("WCC");
                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2.ToString("D4"));
                sb.Append((analysis.Data2 + length - 1).ToString("D4"));
            }
            else if (analysis.Data1 == "D" || analysis.Data1 == "L" || analysis.Data1 == "F")
            {
                sb.Append("WD");
                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2.ToString("D5"));
                sb.Append((analysis.Data2 + length - 1).ToString("D5"));
            }
            else if (analysis.Data1 == "IX" || analysis.Data1 == "IY" || analysis.Data1 == "ID")
            {
                sb.Append("WD");
                sb.Append(analysis.Data1);
                sb.Append(analysis.Data2.ToString("D9"));
                sb.Append((analysis.Data2 + length - 1).ToString("D9"));
            }
            else if (analysis.Data1 == "C" || analysis.Data1 == "T")
            {
                sb.Append("WS");
                sb.Append(analysis.Data2.ToString("D4"));
                sb.Append((analysis.Data2 + length - 1).ToString("D4"));
            }

            sb.Append(Utils.ByteToHexString(values));

            sb.Append(CalculateCrc(sb));
            sb.Append('\u000D');

            return Result.CreateSuccessResult(Encoding.ASCII.GetBytes(sb.ToString()));
        }

        /// <summary>
        /// 检查从PLC反馈的数据，并返回正确的数据内容
        /// </summary>
        /// <param name="response">反馈信号</param>
        /// <returns>是否成功的结果信息</returns>
        public static Result<byte[]> ExtraActualData(byte[] response)
        {
            if (response.Length < 9) return new Result<byte[]>("接收数据长度必须大于9");

            if (response[3] == '$')
            {
                byte[] data = new byte[response.Length - 9];
                if (data.Length > 0)
                {
                    Array.Copy(response, 6, data, 0, data.Length);
                    data = Utils.HexStringToBytes(Encoding.ASCII.GetString(data));
                }
                return Result.CreateSuccessResult(data);
            }
            else if (response[3] == '!')
            {
                int err = int.Parse(Encoding.ASCII.GetString(response, 4, 2));
                string msg = string.Empty;
                switch (err)
                {
                    case 20: msg = "错误未知"; break;
                    case 21: msg = "NACK错误，远程单元无法被正确识别，或者发生了数据错误。"; break;
                    case 22: msg = "WACK 错误:用于远程单元的接收缓冲区已满。"; break;
                    case 23: msg = "多重端口错误:远程单元编号(01 至 16)设置与本地单元重复。"; break;
                    case 24: msg = "传输格式错误:试图发送不符合传输格式的数据，或者某一帧数据溢出或发生了数据错误。"; break;
                    case 25: msg = "硬件错误:传输系统硬件停止操作。"; break;
                    case 26: msg = "单元号错误:远程单元的编号设置超出 01 至 63 的范围。"; break;
                    case 27: msg = "不支持错误:接收方数据帧溢出. 试图在不同的模块之间发送不同帧长度的数据。"; break;
                    case 28: msg = "无应答错误:远程单元不存在. (超时)。"; break;
                    case 29: msg = "缓冲区关闭错误:试图发送或接收处于关闭状态的缓冲区。"; break;
                    case 30: msg = "超时错误:持续处于传输禁止状态。"; break;
                    case 40: msg = "BCC 错误:在指令数据中发生传输错误。"; break;
                    case 41: msg = "格式错误:所发送的指令信息不符合传输格式。"; break;
                    case 42: msg = "不支持错误:发送了一个未被支持的指令。向未被支持的目标站发送了指令。"; break;
                    case 43: msg = "处理步骤错误:在处于传输请求信息挂起时,发送了其他指令。"; break;
                    case 50: msg = "链接设置错误:设置了实际不存在的链接编号。"; break;
                    case 51: msg = "同时操作错误:当向其他单元发出指令时,本地单元的传输缓冲区已满。"; break;
                    case 52: msg = "传输禁止错误:无法向其他单元传输。"; break;
                    case 53: msg = "忙错误:在接收到指令时,正在处理其他指令。"; break;
                    case 60: msg = "参数错误:在指令中包含有无法使用的代码,或者代码没有附带区域指定参数(X, Y, D), 等以外。"; break;
                    case 61: msg = "数据错误:触点编号,区域编号,数据代码格式(BCD,hex,等)上溢出, 下溢出以及区域指定错误。"; break;
                    case 62: msg = "寄存器错误:过多记录数据在未记录状态下的操作（监控记录、跟踪记录等。)。"; break;
                    case 63: msg = "PLC 模式错误:当一条指令发出时，运行模式不能够对指令进行处理。"; break;
                    case 65: msg = "保护错误:在存储保护状态下执行写操作到程序区域或系统寄存器。"; break;
                    case 66: msg = "地址错误:地址（程序地址、绝对地址等）数据编码形式（BCD、hex 等）、上溢、下溢或指定范围错误。"; break;
                    case 67: msg = "丢失数据错误:要读的数据不存在。（读取没有写入注释寄存区的数据。"; break;
                    default: msg = "错误未知"; break;
                }
                return new Result<byte[]>(err, msg);
            }
            else
            {
                return new Result<byte[]>("错误未知");
            }
        }
        #endregion

    }
}
